﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TFDP.Common;
using Microsoft.Data.Schema.Sql.SchemaModel;
using Microsoft.VisualStudio.Data.Schema.Package.Project;
using Microsoft.Data.Schema.SchemaModel;
using TFDP.Common.T4;
using System.IO;
using System.ComponentModel.Composition;
using TFDP.Common.Configuration;

namespace TFDP.Processing
{
    [Export(typeof(IGenerator))]
    public class ProcedureGenerator : IGenerator
    {
        private IProcessor processor;
        private IDatabaseProjectNode project;

        #region IGenerator Members

        public Type SupportedType
        {
            get { return typeof(ISqlTable); }
        }

        public void Initialize(IProcessor processor, IDatabaseProjectNode project)
        {
            this.processor = processor;
            this.project = project;

            DatabaseTemplateProcessor.Instance.TemplateDirectory = project.FindTemplateDirectory();
        }

        public IEnumerable<GeneratedArtifact> Generate(IModelElement element)
        {
            List<GeneratedArtifact> retvals = new List<GeneratedArtifact>();

            if (element.Node(project).ItemProperty(LoodswezenProperties.GenerateCrudProceduresProperty))
            {
                // Insert Procedure
                GeneratedArtifact item = GenerateProcedure(element, "gap_", "Insert", "InsertProcedure.tt");

                if (item != null)
                    retvals.Add(item);

                // Update Procedure
                item = GenerateProcedure(element, "gap_", "Update", "UpdateProcedure.tt");

                if (item != null)
                    retvals.Add(item);

                // Delete Procedure
                item = GenerateProcedure(element, "gap_", "Delete", "DeleteProcedure.tt");

                if (item != null)
                    retvals.Add(item);

                // Load Procedure
                item = GenerateProcedure(element, "gap_", "Load", "LoadProcedure.tt");

                if (item != null)
                    retvals.Add(item);

                // Select Procedure
                item = GenerateProcedure(element, "gap_", "Select", "SelectProcedure.tt");

                if (item != null)
                    retvals.Add(item);

                // SearchVoorBeheer Procedure
                item = GenerateProcedure(element, "gap_", "SearchVoorBeheer", "SearchVoorBeheerProcedure.tt");

                if (item != null)
                    retvals.Add(item);

                // LoadMetHistorie Procedure
                if (element is ISqlTable && ((ISqlTable)element).HasHistoryTable())
                {
                    item = GenerateProcedure(element, "gap_", "LoadMetHistorie", "LoadMetHistorieProcedure.tt");

                    if (item != null)
                        retvals.Add(item);
                }
            }

            return retvals;
        }

        #endregion

        private GeneratedArtifact GenerateProcedure(IModelElement element, string procedurePrefix, string procedureSuffix, string templateFile)
        {
            ISqlTable sourceTable = element as ISqlTable;
            if (sourceTable == null)
                throw new ArgumentException("Invalid type", "element");

            String elementName = String.Format("{0}.{1}{2}{3}", element.Name.Parts[0], procedurePrefix, element.Name.Parts[1], procedureSuffix);

            Logger.LogVerbose("Generating procedure " + elementName);

            // Check if the procedure exists
            ISqlProcedure procedure = project.FindEntity<ISqlProcedure>(elementName);

            // Obtain the properties of the element
            IDatabaseNode node = element.Node(this.project);

            // Execute the template
            string buff = ProcessTemplate<ISqlTable>(templateFile, sourceTable, node);

            if (String.IsNullOrEmpty(buff))
                return null;

            // Determine the path
            string path = (procedure == null) ? ComposeTriggerPath(element.Name, procedurePrefix, procedureSuffix) : procedure.PrimarySource.SourceName;

            // Write output to temporary file
            string tempFile = Path.GetTempFileName();
            File.WriteAllText(tempFile, buff);

            // Return the GenerationItem
            return new GeneratedArtifact(tempFile, path, procedure);
        }

        private string ProcessTemplate<T>(string templateFile, T element, IDatabaseNode node) where T : IModelElement
        {
            return DatabaseTemplateProcessor.Instance.GenerateWithTemplate<T>(templateFile, element, node);
        }

        private string ComposeTriggerPath(ModelIdentifier tableIdentifier, string procedurePrefix, string procedureSuffix)
        {
            string format ="{0}\\Schema Objects\\Schemas\\{1}\\Programmability\\Stored Procedures\\Generated\\{2}{3}{4}.proc.sql";

            if (processor.Settings.ContainsKey("ProcedurePathFormat"))
            {
                string procedurePathFormat = processor.Settings.Get<string>("ProcedurePathFormat");
                if (!String.IsNullOrEmpty(procedurePathFormat))
                    format = procedurePathFormat;
            }

            return String.Format(
                format,
                project.DirectoryName,
                tableIdentifier.Parts[0],
                procedurePrefix,
                tableIdentifier.Parts[1],
                procedureSuffix
            );
        }
    }
}
